﻿/* 
 * Copyright (c) Intel Corporation
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * -- Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * -- Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * -- Neither the name of the Intel Corporation nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE INTEL OR ITS
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Net;
using System.Xml;
using System.Text;
using System.Web;
using ExtensionLoader;
using HttpServer;
using OpenMetaverse;
using OpenMetaverse.Http;
using OpenMetaverse.StructuredData;
using CableBeachMessages;

namespace InventoryServer.Extensions
{
    public class AssetService : IExtension<InventoryServer>
    {
        InventoryServer server;

        public AssetService()
        {
        }

        public bool Start(InventoryServer server)
        {
            this.server = server;

            server.ServiceRegistrationProvider.RegisterService(new Uri(CableBeachServices.ASSETS), CreateCapabilitiesHandler);

            // Metadata request handler
            server.HttpServer.AddHandler("GET", null, @"^/assets/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/metadata",
                GetAssetMetadataHandler);

            // Data request handler
            server.HttpServer.AddHandler("GET", null, @"^/assets/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/data",
                GetAssetDataHandler);

            // Create asset handler. Usually a capability is used to access this, but this path allows trusted access
            // after doing a client certificate check
            server.HttpServer.AddHandler("POST", null, @"^/assets/create_asset", TrustedCreateAssetHandler);

            return true;
        }

        public void Stop()
        {
        }

        void CreateCapabilitiesHandler(Uri identity, ref Dictionary<Uri, Uri> capabilities)
        {
            Uri[] caps = new Uri[capabilities.Count];
            capabilities.Keys.CopyTo(caps, 0);

            for (int i = 0; i < caps.Length; i++)
            {
                Uri cap = caps[i];
                string capName = cap.ToString();

                switch (capName)
                {
                    case CableBeachServices.ASSET_GET_ASSET_METADATA:
                        // Not a complete service endpoint. The requester has to know to append the assetID and "/metadata" to the URL
                        capabilities[cap] = new Uri(server.HttpUri, "/assets/");
                        break;
                    case CableBeachServices.ASSET_GET_ASSET:
                        // Not a complete service endpoint. The requester has to know to append the assetID and "/data" to the URL
                        capabilities[cap] = new Uri(server.HttpUri, "/assets/");
                        break;
                    case CableBeachServices.ASSET_CREATE_ASSET:
                        // Creating an asset is a normal capability
                        capabilities[cap] = server.CreateCapability(CreateAssetHandler, false, identity);
                        break;
                }
            }
        }

        #region Asset Handlers

        void GetAssetMetadataHandler(IHttpClientContext client, IHttpRequest request, IHttpResponse response)
        {
            UUID assetID;
            // Split the URL up into an AssetID and a method
            string[] rawUrl = request.Uri.PathAndQuery.Split('/');

            if (rawUrl.Length >= 3 && UUID.TryParse(rawUrl[rawUrl.Length - 2], out assetID))
            {
                UUID authToken = Utils.GetAuthToken(request);

                // FIXME: Authorization
                if (true)
                {
                    MetadataBlock metadata;
                    BackendResponse storageResponse = server.AssetProvider.TryFetchMetadata(assetID, out metadata);

                    if (storageResponse == BackendResponse.Success)
                    {
                        // If the asset data location wasn't specified in the metadata, specify it
                        // manually here by pointing back to this asset server
                        // First if for when 'Methods = null' that caused null pointer error.
                        if (metadata.Methods != null)
                        {
                            if (!metadata.Methods.ContainsKey("data"))
                            {
                                metadata.Methods["data"] = new Uri(server.HttpUri, "/" + assetID + "/data");
                            }
                        }

                        ServiceHelper.SendResponse(response, metadata.Serialize());
                    }
                    else if (storageResponse == BackendResponse.NotFound)
                    {
                        Logger.Warn("[CableBeachFrontend] Could not find metadata for asset " + assetID);
                        response.Status = HttpStatusCode.NotFound;
                    }
                    else
                    {
                        response.Status = HttpStatusCode.InternalServerError;
                    }
                }
                else
                {
                    response.Status = HttpStatusCode.Forbidden;
                }
            }
            else
            {
                response.Status = HttpStatusCode.NotFound;
            }
        }

        void GetAssetDataHandler(IHttpClientContext client, IHttpRequest request, IHttpResponse response)
        {
            UUID assetID;
            // Split the URL up into an AssetID and a method
            string[] rawUrl = request.Uri.PathAndQuery.Split('/');

            if (rawUrl.Length >= 3 && UUID.TryParse(rawUrl[rawUrl.Length - 2], out assetID))
            {
                UUID authToken = Utils.GetAuthToken(request);

                // FIXME: Authorization
                if (true)
                {
                    byte[] assetData;
                    BackendResponse storageResponse = server.AssetProvider.TryFetchData(assetID, out assetData);

                    if (storageResponse == BackendResponse.Success)
                    {
                        response.Status = HttpStatusCode.OK;
                        response.Status = HttpStatusCode.OK;
                        response.ContentType = "application/octet-stream";
                        response.AddHeader("Content-Disposition", "attachment; filename=" + assetID.ToString());
                        response.ContentLength = assetData.Length;
                        response.Body.Write(assetData, 0, assetData.Length);
                    }
                    else if (storageResponse == BackendResponse.NotFound)
                    {
                        response.Status = HttpStatusCode.NotFound;
                    }
                    else
                    {
                        response.Status = HttpStatusCode.InternalServerError;
                    }
                }
                else
                {
                    response.Status = HttpStatusCode.Forbidden;
                }
            }
            else
            {
                response.Status = HttpStatusCode.BadRequest;
            }
        }

        void TrustedCreateAssetHandler(IHttpClientContext client, IHttpRequest request, IHttpResponse response)
        {
            // FIXME: Implement client certificate checking
            CreateAssetHandler(client, request, response, null);
        }

        void CreateAssetHandler(IHttpClientContext client, IHttpRequest request, IHttpResponse response, object state)
        {
            try
            {
                OSD osdata = OSDParser.DeserializeJson(request.Body);

                if (osdata.Type == OSDType.Map)
                {
                    CreateAssetMessage message = new CreateAssetMessage();
                    message.Deserialize((OSDMap)osdata);

                    CreateAssetReplyMessage reply = new CreateAssetReplyMessage();
                    reply.AssetID = UUID.Zero;
                    reply.AssetUri = null;

                    byte[] assetData = null;
                    try { assetData = Convert.FromBase64String(message.Base64Data); }
                    catch (Exception) { }

                    if (assetData != null && assetData.Length > 0)
                    {
                        BackendResponse storageResponse;

                        if (message.Metadata.ID != UUID.Zero)
                            storageResponse = server.AssetProvider.TryCreateAsset(message.Metadata, assetData);
                        else
                            storageResponse = server.AssetProvider.TryCreateAsset(message.Metadata, assetData, out message.Metadata.ID);

                        if (storageResponse == BackendResponse.Success)
                        {
                            reply.AssetID = message.Metadata.ID;
                            reply.AssetUri = new Uri(server.HttpUri, "/assets/" + reply.AssetID);
                            response.Status = HttpStatusCode.Created;
                        }
                        else if (storageResponse == BackendResponse.NotFound)
                        {
                            response.Status = HttpStatusCode.NotFound;
                        }
                        else
                        {
                            Logger.Error("[AssetService] Asset upload failed, internal error");
                            response.Status = HttpStatusCode.InternalServerError;
                        }
                    }
                    else
                    {
                        Logger.Error("[AssetService] Asset upload failed, could not parse Base64 data");
                        response.Status = HttpStatusCode.BadRequest;
                    }

                    ServiceHelper.SendResponse(response, reply.Serialize());
                }
                else
                {
                    Logger.Error("[AssetService] Asset upload endpoint contacted with invalid data");
                    response.Status = HttpStatusCode.BadRequest;
                }
            }
            catch (Exception ex)
            {
                Logger.Error("[AssetService] Asset upload failed: " + ex.Message);
                response.Status = HttpStatusCode.InternalServerError;
                response.Reason = ex.Message;
            }
        }

        #endregion Asset Handlers
    }
}
